use crate::action::device::{create_device, CreateDeviceError};
use crate::data::message::Message;
use crate::fairing::device::{CaCertificate, DeviceDb};
use crate::guard::userauth::AdminUserData;
use crate::model::device::{Device, DeviceName};
use rcgen::KeyPair;
use rocket::http::Status;
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use rocket::route::Route;
use rocket::serde::json::Json;
use rocket::State;
use rocket_db_pools::{
    sqlx::{self, Acquire},
    Connection,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct NewDeviceResult {
    device: Device,
    key_pair: String,
}

impl From<(Device, KeyPair)> for NewDeviceResult {
    fn from(value: (Device, KeyPair)) -> Self {
        NewDeviceResult {
            device: value.0,
            key_pair: value.1.serialize_pem(),
        }
    }
}

enum CreateDeviceStatus {
    Ok(NewDeviceResult),
    ExistingDeviceName,
    InvalidDeviceName,
    DatabaseError(sqlx::Error),
    CertificateError(rcgen::Error),
    SerialError(rand::Error),
}

impl<'r> Responder<'r, 'static> for CreateDeviceStatus {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match self {
            CreateDeviceStatus::Ok(device) => Response::build_from(Json(device).respond_to(req)?)
                .status(Status::Created)
                .ok(),
            CreateDeviceStatus::ExistingDeviceName => Response::build_from(
                Json(Message {
                    message: String::from("已存在名称相同的设备"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
            CreateDeviceStatus::DatabaseError(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            CreateDeviceStatus::CertificateError(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            CreateDeviceStatus::SerialError(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            CreateDeviceStatus::InvalidDeviceName => Response::build_from(
                Json(Message {
                    message: String::from("设备名为空"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
        }
    }
}

#[derive(Deserialize)]
struct NewDeviceInfo {
    name: DeviceName,
}

#[post("/create", data = "<device_info>")]
async fn create<'r>(
    mut db: Connection<DeviceDb>,
    _user: AdminUserData,
    ca_certificate: &State<CaCertificate>,
    device_info: Json<NewDeviceInfo>,
) -> CreateDeviceStatus {
    let result = async {
        let mut tx = db.begin().await?;

        let device = create_device(
            &mut tx,
            ca_certificate.0.certificate(),
            ca_certificate.0.key_pair(),
            device_info.0.name,
        )
        .await?
        .into();

        tx.commit().await?;

        Ok(device)
    }
    .await;
    match result {
        Ok(user) => CreateDeviceStatus::Ok(user),
        Err(err) => match err {
            CreateDeviceError::ExistingDeviceName => CreateDeviceStatus::ExistingDeviceName,
            CreateDeviceError::InvalidName => CreateDeviceStatus::InvalidDeviceName,
            CreateDeviceError::Database(err) => CreateDeviceStatus::DatabaseError(err),
            CreateDeviceError::CreateCertificate(err) => CreateDeviceStatus::CertificateError(err),
            CreateDeviceError::CreateSerial(err) => CreateDeviceStatus::SerialError(err),
        },
    }
}

pub fn route() -> Vec<Route> {
    routes![create]
}
